文章目录
  1. 一. NeteaseAPM 是什么
  2. 二. NeteaseAPM iOS SDK 的目标
  3. 三 现状
  4. 四 整体设计
  5. 五. 一些关键实现
    1. 1. 开始使用面向切面编程
    2. 3. 桥接模式,从面向过程到面向对象
    3. 4. 使用 NSURLProtocol 监控 UIWebView
  6. 六. 后续工作
  7. 七. 引用链接

一个企业的关键业务应用的性能强大,可以提高竞争力,并取得商业成功。NetsaseAPM 是网易性能数据分析平台,一个用户数据分析平台,支持非侵入式获取应用性能数据,实时展示多个维度分析结果。

一. NeteaseAPM 是什么

Application Performance Management(APM),应用程序性能管理,主要指对企业的关键业务应用进行监测、优化,提高企业应用的可靠性和质量,保证用户得到良好的服务。一个企业的关键业务应用的性能强大,可以提高竞争力,并取得商业成功。NetsaseAPM 是网易性能数据分析平台,一个用户数据分析平台,支持非侵入式获取应用性能数据,实时展示多个维度分析结果。

NetsaseAPM 移动端支持的功能:

  1. 应用性能分析
    对当前应用请求的各项性能指标进行分析,如响应时间,吞吐量,下载速率等,帮助用户全面了解应用性能表现。
  2. 错误分析
    分析应用每个域名的网络错误率及响应码错误率,快速定位应用问题。
  3. 多维分析
    可以组合域名,地理位置,运营商,网络环境等参数,精确定位应用的性能问题。

下面是从移动端收集的数据在 NeteaseAPM Web 平台的展示









二. NeteaseAPM iOS SDK 的目标

  1. 最小侵入:
    只需要启动一次,就可以持续收集网络和交互数据,不需要手动收集数据。
    启动方式:



    启动之后,NeteaseAPM 会插入收集数据的代码到系统调用中,却不会影响用户的使用,在用户的网络消息中的位置如图:


  2. 最大化自由配置:
    • 用户可以看到收集到的数据,选择是否上传到 NeteaseAPM 服务器;
    • 用户可以自己上传数据到 NeteaseAPM 服务器;
  3. 功能特点:
    支持对底层网络库 CFNetwork 的监控;

现状

目前实现的功能:

  • 网络请求的响应时间,下载速率,状态码,错误码,网络状态等数据的收集;
  • 页面加载时间的收集,检查出慢交互页面;

已经接入的应用: 网易新闻,网易云音乐,考拉。

整体设计



NeteaseAPM iOS SDK分为四个部分:

  • Hooker:
    Hooker 负责在用户无感知的情况下替换程序原实现,转发消息回调,完成对系统消息的 hook 和数据的采集。
    Hooker 能否监控到更多更准确的数据,是衡量一个 APM 产品是否优秀的最重要的标准。
    一个 APM 产品监控的数据面越广,收集的数据越细,就越能准确地取得用户的性能数据,帮助产品优化性能。

  • DataBuilder:
    收集监控数据;

  • Persistence:
    缓存监控数据;
  • Poster:
    上传监控数据到NeteaseAPM;

线程模型: 监控数据的保存和发送都在后台队列中执行,不会影响用户线程。

数据上传规则:

  • 可设置允许数据上传的网络环境;
  • 数据支持批量发送,可自定义发送批量和等待间隔;

===================================================================

五. 一些关键实现

APM SDK 使用方式和一般的UI库有很大不同,APM 只需要启动一次即可生效,不需要修改代码。例如:启动APM之后,用户使用 NSURLConnection 执行的网络请求就会在开始连接,得到响应,获取数据等时机被 APM 监控到,而不需要使用者手动添加任何监控代码。要实现这样的目标,Hooker 需要使用一些比较特殊的的方式来实现,下面介绍Hooker使用到的解决方案:

1. 开始使用面向切面编程

APM需要将监控代码插入到系统实现中,完成系统消息的拦截和数据收集。比如我们需要知道 NSURLConnection 在什么时候开始发送请求,就会监控 -[NSURLConnection start] 方法,这就需要在这个方法中插入APM的逻辑,要做到这一点,需要理解面向切面编程的思想。

Aspect Oriented Programming(AOP),面向切面编程,面对的是处理过程中的某个步骤或阶段。在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面编程。借助 AOP,甚至不用修改一行代码,就可以修改现有程序的行为,非常高效。

AOP 基本原理:将一个函数替换为一个新函数,新的函数中插入代码片段,然后执行原函数。




下面分别介绍 Objective-C 和 C 这两种语言支持 AOP 的方式。

Objective-C 对 AOP 的支持非常容易:
一个 Objective-C 的方法调用 [self setFilled:YES] 在运行时和下方的代码等效



系统会通过 SEL 查找对应的函数指针 IMP,所以通过修改SEL对应的IMP,可以方便地hook原方法。
Objective-C 借助这个原理实现的 AOP 有一个形象的名字:
Method Swizzling 醉了的方法 ~~!

C 的 AOP 使用了 dyld(dynamic link editor),在系统加载可执行文件时修改了符号的地址,简单了解一下:
C函数指针的地址可以通过dlsym函数取得,如:



我们需要查找到这个函数指针的地址,查找过程见下图:



fishhook可以方便地查找替换C函数的指针。

### 2. 使用代理模式拦截回调消息
通过 AOP ,可以监控指定类的指定方法了,我们可以取得方法调用的时机了,但是程序中除了方法调用还存在方法回调,这是一种不适合用AOP监控的情况。例如,NSURLConnection 的 start 方法可以通过 Method Swizzling 监控,
但是回调消息的接收者 delegate 的类名不固定,可能是任意一个页面实例,不适合需要指定 Class 的 Method Swizzling。

解决方法是构造一个回调消息的转发者,作为代理,在转发者中收集数据,再转发给用户。下图演示对 NSURLConnection 的监控,MAM IMP 是 start 方法被替换后的实现,ProxyDelegate 就是消息转发者,负责将回调消息转发给 delegate 对象:


NSProxyObjective-C 中除了 NSObject 之外的另一个基类,由于它实现的方法非常少,可以方便地借助消息转发机制转发未实现的消息。

ProxyDelegate 的转发使用系统的消息转发机制:



CFNetwork 的监控也使用了代理模式,获取 stream 读取的数据长度:



3. 桥接模式,从面向过程到面向对象

CFNetwork 是一个 C 语言实现的网络系统框架,虽然使用起来比较麻烦,但是可配置的功能更多。但由于面向过程编程难以扩展的缺点,没有办法选择性地监控和 http 有关的 CFReadStreamRef ,而不影响到来自文件或内存的 CFReadStreamRef

所幸 Objective-C 支持从面向过程到面向对象的桥接:Toll-Free Bridging,允许某些 CoreFoundation类与对应的Objective-C 类互相转换,使我们可以在面向过程编程和面向对象编程两种编程思想中自由切换。

NeteaseAPM 的策略:在系统构造 http stream 时,将一个 NSInputStream 的子类 ProxyStream 桥接为 CFReadStreamRef,返回给用户,达到单独监控 http stream 的效果。



使用 Toll-Free Bridging 时需要重点关注两种不同的内存管理机制的问题。例如 -[NSInputStream propertyForKey] 方法和 CFReadStreamCopyProperty 函数是桥接的,但是它们内存管理方法不同,前者是自动管理,后者是手动管理,后者在调用之后需要使用者在随后手动调用 CFRelease

以 ProxyStream 举例,ProxyStream 是 CFReadStreamRef 的代理,负责管理 http stream 的数据收集。如果用户对一个 http stream 执行如下调用:




ProxyStream的propertyForKey:方法会因为桥接而被调用,ProxyStream需要从original stream中获取正确的property,如果ProxyStream中这么写



那么这个方法返回的结果会被CFReadStreamCopyProperty随后的CFRelease函数错误地释放,引起内存异常。

正确的书写方式应该是



ProxyStream 只需要”转发” CFReadStreamCopyProperty 函数给 original stream 就可以了。

4. 使用 NSURLProtocol 监控 UIWebView

NSURLProtocol 是监控 UIWebView 请求最普遍的解决方案。这里用户会产生多个 NSURLProtocol 会不会冲突的疑问?其实是不会的,因为即使用户注册了 NSURLProtocol ,拦截了 NeteaseAPM 的 NSURLProtocol 的请求,但用户的 NSURLProtocol 在发送请求时使用的的 NSURLConnection 或者 NSURLSession 还是会被 NeteaseAPM 监控到的。

===================================================================

六. 后续工作

  • 支持更多性能数据的收集,如内存,CPU,帧率等;
  • 支持自动诊断性能问题,如卡顿,Out of Memory 等;

七. 引用链接

Objective-C的hook方案: Method Swizzling
使用NSProxy和NSObject设计代理类的差异
fishhook
Toll-free bridging介绍